package com.zzw.redisdistributelock.lock;

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;

/**
 * 分布式锁工具封装
 *
 * @author zzw
 */
@Getter
@Setter
@Component
public class DistributeLock {
    /**
     * 一个线程持有锁的最大时间秒
     */
    public static final int LOCK_MAX_EXIST_TIME = 1;
    /**
     * 锁名称的前缀
     */
    public static final String LOCK_PREX = "distribute_lock_";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String lockPrex;
    private int lockMaxExistTime;
    private DefaultRedisScript<Long> lockScript;
    private DefaultRedisScript<Long> unLockScript;
    public ThreadLocal<String> threadKeyId = new ThreadLocal<String>() {
        protected String initialValue() {
            return UUID.randomUUID().toString();
        }
    };

    public DistributeLock() {
        this.init();
    }

    public DistributeLock(StringRedisTemplate redisTemplate) {
        this(redisTemplate, LOCK_PREX, LOCK_MAX_EXIST_TIME);
    }

    public DistributeLock(StringRedisTemplate redisTemplate, String lockPrex) {
        this(redisTemplate, lockPrex, LOCK_MAX_EXIST_TIME);
    }

    public DistributeLock(StringRedisTemplate redisTemplate, int lockMaxExistTime) {
        this(redisTemplate, LOCK_PREX, lockMaxExistTime);
    }

    public DistributeLock(StringRedisTemplate redisTemplate, String lockPrex, int lockMaxExistTime) {
        this.stringRedisTemplate = redisTemplate;
        this.lockPrex = lockPrex;
        this.lockMaxExistTime = lockMaxExistTime;
        this.init();
    }

    public void init() {
        this.lockScript = new DefaultRedisScript();
        this.lockScript.setLocation(new ClassPathResource("lua/lock.lua"));
        this.lockScript.setResultType(Long.class);

        this.unLockScript = new DefaultRedisScript();
        this.unLockScript.setLocation(new ClassPathResource("lua/unlock.lua"));
        this.unLockScript.setResultType(Long.class);
    }

    /**
     * 获取分布式锁
     *
     * @param key           锁
     * @param lockExistTime 锁持有超时时间(秒)
     * @return 返回获取成功或失败
     */
    public Lock lock(final String key, int lockExistTime) {
        String lockKey = this.getKey(key);
        Long accquire = this.stringRedisTemplate.execute(this.lockScript, Collections.singletonList(lockKey), threadKeyId.get(), String.valueOf(lockExistTime * 1000));
        return accquire > 0 ? Lock.SUCCESS : Lock.FAILED;
    }

    /**
     * 获取分布式锁
     * 直到获取到成功为止
     * 重试300次如果还是获取不到锁则返回失败
     *
     * @param key           锁的键
     * @param lockExistTime 锁持有超时时间(秒)
     * @return 返回锁锁枚举
     */
    public Lock tryLock(final String key, int lockExistTime) {
        int tryCount = 300; //尝试次数
        while (tryCount > 0) {
            tryCount--;
            if (this.lock(key, lockExistTime).isSuccess()) {
                return Lock.SUCCESS;
            }
            try {
                //短暂休眠，nano避免出现活锁
                Thread.sleep(10L, (int) (Math.random() * 500));
            } catch (InterruptedException e) {
                return Lock.FAILED;
            }
        }
        return Lock.FAILED;
    }

    /**
     * 获取分布式锁
     *
     * @param key 锁
     * @return 返回获取成功或失败
     */
    public Lock lock(final String key) {
        return this.lock(key, this.lockMaxExistTime);
    }

    /**
     * 尝试获取分布式锁
     * 直到获取成功或者超时
     *
     * @param key 锁
     * @return 返回获取成功或失败
     */
    public Lock tryLock(final String key) {
        return this.tryLock(key, this.lockMaxExistTime);
    }

    /**
     * 释放锁，同时要考虑当前锁是否为自己所有，以下情况
     * 会导致当前线程失去锁
     * 1.线程执行时间超过超时的时间，导致此锁被其它线程拿走，此时用户不可以执行删除。
     *
     * @param key
     */
    public void unlock(String key) {
        this.stringRedisTemplate.execute(this.unLockScript, Collections.singletonList(this.getKey(key)), this.threadKeyId.get());
    }

    /**
     * 生成锁的key
     *
     * @param key
     * @return 锁的键
     */
    public String getKey(String key) {
        StringBuilder sb = new StringBuilder();
        sb.append(this.lockPrex).append(key);
        return sb.toString();
    }

    /**
     * 判断是否获取锁成功
     *
     * @param lock
     * @return
     */
    public boolean isSuccess(Lock lock) {
        return Objects.nonNull(lock) && lock.isSuccess();
    }

    /**
     * 判断是否获取锁失败
     *
     * @param lock
     * @return
     */
    public boolean isFailed(Lock lock) {
        return !isSuccess(lock);
    }
}
